package com.team.sns.persistence.impl;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.LinkedHashMap;
import java.util.List;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.team.sns.entities.QueryResult;
import com.team.sns.persistence.Persistence;

@SuppressWarnings("unchecked")
@Transactional
public abstract class JPAProvider<T> implements Persistence<T> {
	
	protected Class<T> entityClass = (Class<T>) ((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
	@PersistenceContext protected EntityManager em;
	
	public void clear() {
		em.clear();
	}

	public void delete(Serializable id) {
		em.remove(em.getReference(this.entityClass, id));
	}

	public void delete(Serializable... ids) {
		for (Serializable id : ids) {
			this.delete(id);
		}
	}
	
	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public T findById(Serializable id) {
		return em.find(this.entityClass, id);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public Long getCount() {
		StringBuffer hql = new StringBuffer();
		hql.append("select count(o) from ").append(getEntityName(this.entityClass)).append(" o");
		return (Long)em.createQuery(hql.toString()).getSingleResult();
	}

	@SuppressWarnings("static-access")
	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public Long getCount(String whereHql, Object[] params) {
		StringBuffer hql = new StringBuffer();
		hql.append("select count(o) from ").append(getEntityName(this.entityClass)).append(" o ")
		   .append(whereHql == null || "".equals(whereHql) ? "" : " where " + whereHql);
		
		Query query = em.createQuery(hql.toString());
		this.setQueryParams(query, params);
		return (Long) query.getSingleResult();
	}
	
	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<T> getResultList() {
		return this.getResultList(-1, -1, null, null, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<T> getResultList(int startIndex, int maxResult) {
		return this.getResultList(startIndex, maxResult, null, null, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<T> getResultList(int startIndex, int maxResult,
			String whereHql, Object[] params) {
		return this.getResultList(startIndex, maxResult, whereHql, params, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<T> getResultList(int startIndex, int maxResult,
			LinkedHashMap<String, String> orderBy) {
		return this.getResultList(startIndex, maxResult, null, null, orderBy);
	}

	@SuppressWarnings("static-access")
	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<T> getResultList(int startIndex, int maxResult,
			String whereHql, Object[] params,
			LinkedHashMap<String, String> orderBy) {
		String hql = "select o from " + this.getEntityName(entityClass) + " o "
				        + (whereHql == null || "".equals(whereHql) ? "" : " where "
				        + whereHql )+ this.buildOrderBy(orderBy);
		Query query = em.createQuery(hql);
		this.setQueryParams(query, params);
		if (startIndex == -1 || maxResult == -1) {
			query.setFirstResult(startIndex).setMaxResults(maxResult);
		}
		
		return query.getResultList();
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public QueryResult<T> getScrollData() {
		return this.getScrollData(-1, -1, null, null, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public QueryResult<T> getScrollData(int startIndex, int maxResult) {
		return this.getScrollData(startIndex, maxResult, null, null, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public QueryResult<T> getScrollData(int startIndex, int maxResult,
			String whereHql, Object[] params) {
		return this.getScrollData(startIndex, maxResult, whereHql, params, null);
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public QueryResult<T> getScrollData(int startIndex, int maxResult,
			LinkedHashMap<String, String> orderBy) {
		return this.getScrollData(startIndex, maxResult, null, null, orderBy);
	}

	@SuppressWarnings("static-access")
	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public QueryResult<T> getScrollData(int startIndex, int maxResult,
			String whereHql, Object[] params,
			LinkedHashMap<String, String> orderBy) {
		QueryResult<T> qr = new QueryResult<T>();
		qr.setResultList(this.getResultList(startIndex, maxResult, whereHql, params, orderBy));
		qr.setTotalRecord(this.getCount(whereHql, params));
		return qr;
	}

	@Transactional(readOnly=true, propagation = Propagation.NOT_SUPPORTED)
	public List<Object[]> queryByHql(String hql) {
		return em.createQuery(hql).getResultList();
	}

	public void save(T obj) {
		em.persist(obj);
	}

	public void update(T obj) {
		em.merge(obj);
	}
	
	protected static void setQueryParams(Query query, Object[] params) {
		if (params != null && params.length > 0) {
			for (int i = 1; i <= params.length; i ++) {
				query.setParameter(i, params[i - 1]);
			}
		}
	}
	
	protected static String buildOrderBy(LinkedHashMap<String, String> orderBy) {
		StringBuffer orderByHql = null;
		if (orderBy != null && orderBy.size() > 0) {
			orderByHql = new StringBuffer(" order by ");
			for (String key : orderBy.keySet()) {
				orderByHql.append("o").append(key).append(" ").append(orderBy.get(key)).append(",");
			}
			orderByHql.deleteCharAt(orderByHql.length() - 1);
			return orderByHql.toString();
		}
		return "";
	}
	
	/**
	 * 鑾峰彇�?�炰綋鐨勫悕绉�
	 * @param <E>
	 * @param clazz 瀹炰綋绫�?
	 * @return
	 */
	protected static <E> String getEntityName(Class<E> clazz){
		String entityname = clazz.getSimpleName();
		Entity entity = clazz.getAnnotation(Entity.class);
		if(entity.name()!=null && !"".equals(entity.name())){
			entityname = entity.name();
		}
		return entityname;
	}
	
	protected static <E> String getCountField(Class<E> clazz){
		String out = "o";
		try {
			PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
			for(PropertyDescriptor propertyDesc : propertyDescriptors){
				Method method = propertyDesc.getReadMethod();
				if(method != null && method.isAnnotationPresent(EmbeddedId.class)){					
					PropertyDescriptor[] ps = Introspector.getBeanInfo(propertyDesc.getPropertyType()).getPropertyDescriptors();
					out = "o." + propertyDesc.getName() + "." + (!ps[1].getName().equals("class")? ps[1].getName(): ps[0].getName());
					break;
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
        return out;
	}

}
